﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Xml;

using ClassLibrary;


namespace Core
{
    /*
     * Core class for saving all data about model 
     * and processing all calculations
    */
    public class Model
    {
        public string model_name { set; get; }

        public string model_path { set; get; }

        int amount_tables { set; get; }
        int amount_links { set; get; }
        int amount_species { set; get; }
                
        public Dictionary<string, ChromosomeTable> model_chromosome_tables = new Dictionary<string, ChromosomeTable>();
        public Dictionary<string, GeneTable> model_gene_tables = new Dictionary<string, GeneTable>();
        public Dictionary<string, PhenotypeTable> model_phenotype_tables = new Dictionary<string, PhenotypeTable>();
        public Dictionary<string, NaturalSelectionTable> model_natural_selection_tables = new Dictionary<string, NaturalSelectionTable>();

        public Dictionary<string, ChromosomeGeneLink> model_chromosome_gene_links = new Dictionary<string, ChromosomeGeneLink>();
        public Dictionary<string, GenomePhenotypeLink> model_genome_phenotype_links = new Dictionary<string, GenomePhenotypeLink>();
        public Dictionary<string, NaturalSelectionLink> model_natural_selection_links = new Dictionary<string, NaturalSelectionLink>();
        public Dictionary<string, ReproductionLink> model_reproduction_links = new Dictionary<string, ReproductionLink>();

        public Dictionary<string, CrossingoverLink> model_crossingover_links = new Dictionary<string, CrossingoverLink>();

        public Dictionary<string, Species> model_species = new Dictionary<string, Species>();


        /*
         * Constructor
         * Args: path to file with model description
         * Do: loads model data from XML files into memory
        */
        public Model(string path_to_model)
        {
            model_path = path_to_model;
            if (File.Exists(model_path))
            {
                readModelData();

                checkModelLinks();

                initModelSpecies();
            }
            else
            {
                createNewModel(model_path);    
            }
        }


        /*
         * Method for creating new model
        */
        private void createNewModel(string path)
        {
            model_name = model_path.Split('\\').Last().Split('.')[0];
            Directory.CreateDirectory(Directory.GetParent(model_path) + "\\Tables\\GeneTables");
            Directory.CreateDirectory(Directory.GetParent(model_path) + "\\Links\\ChromosomeGeneLinks");
            Directory.CreateDirectory(Directory.GetParent(model_path) + "\\Links\\GenomePhenotypeLinks");
            Directory.CreateDirectory(Directory.GetParent(model_path) + "\\Links\\NaturalSelectionLinks");
            Directory.CreateDirectory(Directory.GetParent(model_path) + "\\Links\\CrossingoverLinks");
            Directory.CreateDirectory(Directory.GetParent(model_path) + "\\Species");
            Directory.CreateDirectory(Directory.GetParent(model_path) + "\\Tables\\ChromosomeTables");
            Directory.CreateDirectory(Directory.GetParent(model_path) + "\\Tables\\PhenotypeTables");
            Directory.CreateDirectory(Directory.GetParent(model_path) + "\\Tables\\NaturalSelectionTables");
            Directory.CreateDirectory(Directory.GetParent(model_path) + "\\Links\\ReproductionLinks");
            XmlTextWriter writer = new XmlTextWriter(model_path, null);
            writer.Formatting = Formatting.Indented;
            writer.WriteStartDocument();
            writer.WriteStartElement("model");
            writer.WriteAttributeString("Name", model_name);
            writer.WriteAttributeString("AmountTables", "0");
            writer.WriteAttributeString("AmountLinks", "0");
            writer.WriteAttributeString("AmountSpecies", "0");
            writer.WriteStartElement("tables");
            writer.WriteAttributeString("AmountGeneTables", "0");
            writer.WriteAttributeString("AmountChomosomeTables", "0");
            writer.WriteAttributeString("AmountPhenotypeTables", "0");
            writer.WriteAttributeString("AmountNaturalSelectionTables", "0");
            writer.WriteStartElement("gene_tables");
            writer.WriteEndElement();
            writer.WriteStartElement("chromosome_tables");
            writer.WriteEndElement();
            writer.WriteStartElement("phenotype_tables");            
            writer.WriteEndElement();
            writer.WriteStartElement("natural_selection_tables");
            writer.WriteEndElement();
            writer.WriteEndElement();
            writer.WriteStartElement("links");
            writer.WriteAttributeString("AmountChromosomeAndGeneLinks", "0");
            writer.WriteAttributeString("AmountGenomePhenotypeLinks", "0");
            writer.WriteAttributeString("AmountNaturalSelectionLinks", "0");
            writer.WriteAttributeString("AmountReproductionLinks", "0");
            writer.WriteAttributeString("AmountCrossingoverLinks", "0");
            writer.WriteStartElement("chromosome_and_gene_links");
            writer.WriteEndElement();
            writer.WriteStartElement("genome_and_phenotype_links");
            writer.WriteEndElement();
            writer.WriteStartElement("natural_selection_links");
            writer.WriteEndElement();
            writer.WriteStartElement("reproduction_links");
            writer.WriteEndElement();
            writer.WriteStartElement("crossingover_links");
            writer.WriteEndElement();
            writer.WriteEndElement();
            writer.WriteStartElement("species");
            writer.WriteAttributeString("AmountSpecies", "0");
            writer.WriteEndElement();
            writer.WriteEndElement();
            writer.WriteEndDocument();
            writer.Close();
        }

        /* 
         * Method (auxiliary #1 for constructor)
         * Args: no
         * Do: loads all data for model
        */
        public void readModelData()
        {
            // open XML file                         
                XmlDocument doc = new XmlDocument();
                doc.PreserveWhitespace = false;
                doc.LoadXml(File.ReadAllText(model_path));                         

                // reading model's attributes                
                model_name = doc.DocumentElement.GetAttribute("Name");
                amount_tables = Convert.ToInt32(doc.DocumentElement.GetAttribute("AmountTables"));
                amount_links = Convert.ToInt32(doc.DocumentElement.GetAttribute("AmountLinks"));
                amount_species = Convert.ToInt32(doc.DocumentElement.GetAttribute("AmountSpecies"));

                readModelTables(doc);
                readModelLinks(doc);
                readModelSpecies(doc);                                     
        }

        /* 
         * Method (auxiliary #1.1 for constructor)
         * Args: XmlDocument doc opened to read Model description
         * Do: loads data for all tables
        */
        public void readModelTables(XmlDocument doc)
        {
            if (amount_tables == 0)
            {
                return;
            }

            // reading "tables" node            
            int amount_gene_tables = Convert.ToInt32(doc.GetElementsByTagName("tables")[0].Attributes[0].Value);
            int amount_chromosome_tables = Convert.ToInt32(doc.GetElementsByTagName("tables")[0].Attributes[1].Value);
            int amount_phenotype_tables = Convert.ToInt32(doc.GetElementsByTagName("tables")[0].Attributes[2].Value);
            int amount_natural_selection_tables = Convert.ToInt32(doc.GetElementsByTagName("tables")[0].Attributes[3].Value);


            // getting gene_tables
            if (amount_gene_tables != 0)
            {
                // reading "gene_tables" node
                foreach (XmlNode xml_node in doc.GetElementsByTagName("gene_tables")[0].ChildNodes)
                {
                    model_gene_tables.Add(xml_node.Attributes[0].Value, new GeneTable(xml_node.Attributes[0].Value));
                }                            
            }


            // getting chromosome_tables
            if (amount_chromosome_tables != 0)
            {
                // reading "chromosome_tables" node                
                foreach (XmlNode xml_node in doc.GetElementsByTagName("chromosome_tables")[0].ChildNodes)
                {
                    model_chromosome_tables.Add(xml_node.Attributes[0].Value, new ChromosomeTable(xml_node.Attributes[0].Value));
                }             
            }


            // getting phenotype_tables
            if (amount_phenotype_tables != 0)
            {
                // reading "phenotype_tables" node                
                foreach (XmlNode xml_node in doc.GetElementsByTagName("phenotype_tables")[0].ChildNodes)
                {
                    model_phenotype_tables.Add(xml_node.Attributes[0].Value, new PhenotypeTable(xml_node.Attributes[0].Value));
                }
            }


            // getting natural_selection_tables
            if (amount_natural_selection_tables != 0)
            {
                // reading "phenotype_tables" node                
                foreach (XmlNode xml_node in doc.GetElementsByTagName("natural_selection_tables")[0].ChildNodes)
                {
                    model_natural_selection_tables.Add(xml_node.Attributes[0].Value, new NaturalSelectionTable(xml_node.Attributes[0].Value));
                }
            }  
        }


        /*
         * Method (auxiliary #1.2 for constructor)
         * Args: XmlDocument doc opened for read Model description
         * Do: loads data for all links
        */
        public void readModelLinks(XmlDocument doc)
        {
            if (amount_links == 0)
            {
                return;
            }


            // reading "links" node      

            // reading "chromosome_gene_links"
            int amount_chromosome_gene_links = Convert.ToInt32(doc.GetElementsByTagName("links")[0].Attributes[0].Value);
            if (amount_chromosome_gene_links != 0)
            {
                // reading "chromosome_and_gene_links" node
                XmlNodeList xml_node_list = doc.GetElementsByTagName("chromosome_and_gene_links")[0].ChildNodes;
                foreach (XmlNode xml_node in xml_node_list)
                {
                    model_chromosome_gene_links.Add(xml_node.Attributes[0].Value, new ChromosomeGeneLink(xml_node.Attributes[0].Value));
                }
            }


            // reading "genome_phenotype_links"
            int amount_genome_phenotype_links = Convert.ToInt32(doc.GetElementsByTagName("links")[0].Attributes[1].Value);
            if (amount_genome_phenotype_links != 0)
            {
                // reading "genome_phenotype_links" node 
                XmlNodeList xml_node_list = doc.GetElementsByTagName("genome_and_phenotype_links")[0].ChildNodes;
                foreach (XmlNode xml_node in xml_node_list)
                {                    
                    model_genome_phenotype_links.Add(xml_node.Attributes[0].Value, new GenomePhenotypeLink(xml_node.Attributes[0].Value));
                }
            }


            // reading "natural_selection_inks"
            int natural_selection_inks = Convert.ToInt32(doc.GetElementsByTagName("links")[0].Attributes[2].Value);
            if (natural_selection_inks != 0)
            {
                // reading "natural_selection_inks" node 
                XmlNodeList xml_node_list = doc.GetElementsByTagName("natural_selection_links")[0].ChildNodes;
                foreach (XmlNode xml_node in xml_node_list)
                {
                    model_natural_selection_links.Add(xml_node.Attributes[1].Value, new NaturalSelectionLink(xml_node.Attributes[1].Value));
                }
            }


            // reading "reproduction_inks"
            int reproduction_inks = Convert.ToInt32(doc.GetElementsByTagName("links")[0].Attributes[3].Value);
            if (reproduction_inks != 0)
            {
                // reading "reproduction_inks" node 
                XmlNodeList xml_node_list = doc.GetElementsByTagName("reproduction_links")[0].ChildNodes;
                foreach (XmlNode xml_node in xml_node_list)
                {
                    model_reproduction_links.Add(xml_node.Attributes[1].Value, new ReproductionLink(xml_node.Attributes[1].Value));
                }
            }


            // reading "crossingover_links"
            int crossingover_links = Convert.ToInt32(doc.GetElementsByTagName("links")[0].Attributes[4].Value);
            if (crossingover_links != 0)
            {
                // reading "crossingover_links" node 
                XmlNodeList xml_node_list = doc.GetElementsByTagName("crossingover_links")[0].ChildNodes;
                foreach (XmlNode xml_node in xml_node_list)
                {
                    model_crossingover_links.Add(xml_node.Attributes[0].Value, new CrossingoverLink(xml_node.Attributes[0].Value));
                }
            }
        }


       /*
        * Method (auxiliary #1.3 for constructor)
        * Args: XmlDocument doc opened to read Model description
        * Do: loads data for all links
       */
        public void readModelSpecies(XmlDocument doc)
        {
            if (amount_species == 0)
            {
                return;
            }

            // reading "species" node            
            int real_amount_species = Convert.ToInt32(doc.GetElementsByTagName("species")[0].Attributes[0].Value);
            if (real_amount_species != 0)
            {
                // creating species massive (as a dictionary) and bonding to tables and links                
                ChromosomeTable ch_table;
                GeneTable ge_table;
                ChromosomeGeneLink ch_ge_link;
                PhenotypeTable ph_table;
                GenomePhenotypeLink ge_ph_link;
                NaturalSelectionTable na_se_table;
                CrossingoverLink cr_link;

                foreach (XmlNode xml_node in doc.GetElementsByTagName("species")[0].ChildNodes)
                {
                    model_species.Add(xml_node.Attributes[0].Value, new Species(xml_node.Attributes[0].Value));
  
                    ch_table = model_chromosome_tables[xml_node.Attributes[1].Value];
                    ge_table = model_gene_tables[xml_node.Attributes[2].Value];
                    ch_ge_link = model_chromosome_gene_links[xml_node.Attributes[3].Value];
                    ph_table = model_phenotype_tables[xml_node.Attributes[4].Value];
                    ge_ph_link = model_genome_phenotype_links[xml_node.Attributes[5].Value];
                    na_se_table = model_natural_selection_tables[xml_node.Attributes[6].Value];
                    cr_link = model_crossingover_links[xml_node.Attributes[7].Value];

                    model_species[xml_node.Attributes[0].Value].bindSpecies(ch_table, ge_table, ch_ge_link, ph_table, ge_ph_link,
                                                                            na_se_table, cr_link, model_natural_selection_links,
                                                                            model_reproduction_links);
                }                
            }            
        }


       /*
        * Method 
        * Args: no
        * Do: set all links
       */
        public void checkModelLinks()
        {
            ChromosomeGeneLink chromosome_gene_link;
            foreach ( string key in model_chromosome_gene_links.Keys)
            {
                chromosome_gene_link = model_chromosome_gene_links[key];

                bool gene_table_exist = model_gene_tables.ContainsKey(chromosome_gene_link.path_to_gene_table);
                bool chromosome_table_exist = model_chromosome_tables.ContainsKey(chromosome_gene_link.path_to_chromosome_table);
                bool linked_tables_exist = gene_table_exist && chromosome_table_exist;

                if ( linked_tables_exist )
                {                    
                    chromosome_gene_link.available = true;                    
                } else
                {
                    chromosome_gene_link.available = false;
                }
            }


            GenomePhenotypeLink genome_phenotype_link;
            foreach (string key in model_genome_phenotype_links.Keys)
            {
                genome_phenotype_link = model_genome_phenotype_links[key];

                bool gene_table_exist = model_gene_tables.ContainsKey(genome_phenotype_link.path_to_gene_table);
                bool phenotype_table_exist = model_phenotype_tables.ContainsKey(genome_phenotype_link.path_to_phenotype_table);
                bool linked_tables_exist = gene_table_exist && phenotype_table_exist;

                if (linked_tables_exist)
                {
                    genome_phenotype_link.available = true;
                }
                else
                {
                    genome_phenotype_link.available = false;
                }
            }


            NaturalSelectionLink natural_selection_link;
            foreach (string key in model_natural_selection_links.Keys)
            {
                natural_selection_link = model_natural_selection_links[key];

                bool phenotype_table_exist = model_phenotype_tables.ContainsKey(natural_selection_link.path_to_phenotype_table);
                bool linked_tables_exist = phenotype_table_exist;

                if (linked_tables_exist)
                {
                    natural_selection_link.available = true;
                }
                else
                {
                    natural_selection_link.available = false;
                }
            }


            ReproductionLink reproduction_link;
            foreach (string key in model_reproduction_links.Keys)
            {
                reproduction_link = model_reproduction_links[key];

                bool phenotype_table_exist = model_phenotype_tables.ContainsKey(reproduction_link.path_to_phenotype_table);
                bool linked_tables_exist = phenotype_table_exist;

                if (linked_tables_exist)
                {
                    reproduction_link.available = true;
                }
                else
                {
                    reproduction_link.available = false;
                }
            }


            CrossingoverLink crossingover_link;
            foreach (string key in model_crossingover_links.Keys)
            {
                crossingover_link = model_crossingover_links[key];

                bool chromosome_table_exist = model_chromosome_tables.ContainsKey(crossingover_link.path_to_chromosome_table);
                bool linked_tables_exist = chromosome_table_exist;

                if (linked_tables_exist)
                {
                    crossingover_link.available = true;
                }
                else
                {
                    crossingover_link.available = false;
                }
            }
        }


        /*
        * Method 
        * Do: initialization for all species
       */
        public void initModelSpecies()
        {
            foreach (string key in model_species.Keys)
            {
                model_species[key].initSpecies();
            }
        }


        /*
         * Method 
         * Do : makes one step in modelling
         * Args: no
        */ 
        public void ModelOneStep()
        {
            foreach( Species species in model_species.Values)
            {
                species.spTakeNaturalSelectionLinks();
                species.spTakeReproductionLinks();
                species.spCountPhenotypeCharacteristics();
                species.spGetEvents();
                species.spGatherOneStepStatistics();
                species.spDoNextModellingStep();
                GC.Collect();
            }
        }
    }
}